今天來介紹 Context 跟 middleware
在 trpc 中你可以透過 context 去放置一些 db connection Authorization info,透過 context 傳遞到所有 porcedures 中,或是一些你想管理的共用 function 你也可以隨你喜好添加,可以想像 context 就是後端的 state management。
首先你需要訂一個 createContext function,那這邊筆者習慣用 prisma 所以會在 createContext 中 return 他,prisma 如果不會的朋友也沒關係,明日會再給大家簡單教學,這邊主要是先給大家看觀念。
之後記得在 initTRPC call context() 同時記得把 createContext type 帶進去。
// ~server/api/trpc.ts
import { initTRPC, type inferAsyncReturnType } from '@trpc/server';
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';
export const createContext = async (opts: CreateNextContextOptions) => {
return {
};
};
const t = initTRPC.context<typeof createTRPCContext>().create({
errorFormatter(opts) {
const { shape, error } = opts
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? error.cause.flatten()
: null
}
}
}
});
上面只有定義 function,但還沒給 trpc 去引用,而 trpc call context 的地方如果是 next 的話會是在 api route 中如下:
// ~api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '@/server/api/root';
import { createTRPCContext } from '@/server/api/trpc';
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
});
之後我們定義 prisma 的 db connect,然後放到 createContext 中。
// ~server/db.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log:
process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
// ~server/api/trpc.ts
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
return {
prisma
};
};
接著我們查看之前的 greeting porcedures ,你會發現第二個參數 ctx 其實就是 createContext return 的內容,這樣我們就可以在所有 porcedures 中使用 prisma 拉~

於是我們再加一個 addPosts 的 porcedures
export const appRouter = router({
//...
getPosts: publicProcedure
.query(async ({ ctx }) => {
const { prisma } = ctx
const posts = await prisma.post.findMany({})
return posts
}),
});
然後我們看一下 api 結果,成功 return 我們要的內容了~

另一個今日重點就是 middleware 以下方 demo 為例,我們定義個 interface Context,透過 t.middleware create middleware。
在 isAdmin 可以透過 context 內容驗證 isAdmin 如果沒有就 throw error,成功則把 context 內容傳下去,之後把 isAdmin 這支 middleware 交給 publicProcedure 去 use,這時你就會拿到有 auth 驗證的 adminProcedure,結合上面 createTRPCContext 概念我們先 mock 一個 user 。
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
return {
// some mock data
user:{
id:1,
isAdmin:true,
name:'Danny'
}
};
};
const t = initTRPC.context<typeof createTRPCContext>().create();
export const middleware = t.middleware;
export const publicProcedure = t.procedure;
export const router = t.router;
const isAdmin = middleware(async (opts) => {
const { ctx } = opts;
if (!ctx.user?.isAdmin) {
throw new Error('UNAUTHORIZED');
}
return opts.next({
ctx: {
user: ctx.user,
},
});
});
export const adminProcedure = publicProcedure.use(isAdmin);
而 adminProcedure 用法跟 publicProcedure 一樣,如此以來就成功定義好一個 protect 的 router 了
const adminRouter = router({
foo: publicProcedure.query(() => 'bar'),
howAmI: adminProcedure.query(({ ctx }) => ctx.user.name), // danny
});
同時你也可以做 logging
const loggerMiddleware = middleware(async (opts) => {
const start = Date.now();
const result = await opts.next();
const durationMs = Date.now() - start;
const meta = { path: opts.path, type: opts.type, durationMs };
result.ok
? console.log('OK request timing:', meta)
: console.error('Non-OK request timing', meta);
return result;
});
export const loggedProcedure = publicProcedure.use(loggerMiddleware);
import { loggedProcedure, router } from './trpc';
export const appRouter = router({
foo: loggedProcedure.query(() => 'bar'),
abc: loggedProcedure.query(() => 'def'),
});
今天內容比較簡單希望讀者可以學習到 context 概念跟 middleware 精髓,筆者相信讀者們每個都比我優秀有興趣的朋友可以玩玩看~
https://trpc.io/docs/server/middlewares
✅ 前端社群 :
https://lihi3.cc/kBe0Y